<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        let target=null
        //封装一个Vue实现数据代理
        function Vue({ data }) {
            //在Vue内部把数据放在了vm._data上
            this._data = data
            //但是模板中是直接去vm身上获取数据的,每次都要_data.xxx不方便,
            //所以我们可以在vm上放一份数据进行数据代理,代理_data中的数据
            //当获取vm的数据时,其实获取的还是_data中的数据,设置vm的数据时,设置的也是_data中的数据

            //1.遍历_data的数据 在vm上放一份    
            for (let key in this._data) {
                //给已经存在的实例对象vm 扩展一些属性,并且设置存取器属性,需要使用Object.defineProperty
                Object.defineProperty(this, key, {
                    get: function proxyGetter() {
                        //当被读取的时候,去_data中获取值
                        return this._data[key]
                    },
                    set: function proxySetter(val) {
                        this._data[key] = val
                    }
                })
            }
            //调用observe的方法,可以判断当前数据是否需要被数据劫持,如果需要进行数据劫持,则我们需要去实例化一个Observer
            observe(this._data)
        }
        //封装一个方法,判断当前的数据是否需要完成数据劫持
        function observe(data) {
            if (typeof data !== "object" || data === null) return
            //   实例化一个发布者
            new Observer(data)
        }

        // 封装一个Observer类（创建发布者-数据劫持）
        class Observer {
            constructor(data) {
                this.data = data
                // 判断当前需要被劫持的值是数组还是对象，然后分发到不同的方法中
                if (Array.isArray(this.data)) {
                    this.observeArray(this.data)
                } else {
                    this.walk(this.data)
                }
            }
            //发布者Observer类上 处理数组格式的劫持方法
            observeArray(data) {
                //当前还是要遍历一下数组,如果数组的值是对象,还要对这个对象进行劫持,
                //如果数组的值是基本类型则不需要任何处理,因为vue在其他位置已经对变更方法封装了
                data.forEach((item) => {
                    observe(item)
                })
            }
            //发布者Observer类上 处理对象格式的劫持方法
            walk(data) {
                //遍历当前的对象,对对象内部的属性一一做一个劫持
                for (let key in data) {
                    defineReactive(data, key)
                }
            }
        }
        //封装一个数据劫持的功能核心
        function defineReactive(data, key) {
            let value = data[key]
            //每次要开始数据劫持之前,我们就会对某个被劫持的属性创建一个 dep(中间人),这个dep可以收集当前数据的所有依赖项,并且还拥有了通知所有依赖项更新的方法
            let dep = new Dep()
            //调用observe方法,判断当前value值是不是一个对象或者数据来进行深层次的劫持
            observe(value)

            Object.defineProperty(data, key, {
                get: function reactiveGetter() {
                    //因为只有模板收集数据的数据的时候,这个模板才是依赖项(订阅者),我们才会收集这个依赖,
                    //所以我们要先进行判断 然后再收集依赖
                    if(target){
                        dep.depend(target)
                    }
                    return value
                },
                set: function reactiveSetter(newVal) {
                    //判断如果新值和旧值一样,则不做任何操作
                    if (newVal === value) return
                    //否则给value赋值,当再次取值的时候,得到就是最新的value
                    value = newVal
                    //通知模板重新获取数据
                    console.log("值的修改被劫持了,模板赶紧重新获取值,然后更新");
                    //调用当前数据对应的dep的notify方法,通知模板更新
                    dep.notify()
                }
            })
        }

        //封装一个 发布订阅中心(中间人)的类
        class Dep {
            constructor() {
                //每次实例化一个中间人dep的时候,就要创建一个新的数组用来收集所有的依赖(订阅者)
                this.sub = []
            }

            //中间人必须存在的方法1:收集依赖
            depend(watcher) {
                this.sub.push(watcher)
            }
            //中间人必须存在的方法2:通知所有的依赖更新
            notify() {
                // 遍历当前sub中所有的watcher，调用watcher的update方法，进行更新即可
                this.sub.forEach((item)=>{
                    item.update()
                })
             }
        }
        // 封装一个订阅者（每次模板中使用一个数据的时候就是一个订阅者）
        class Watcher{
            constructor(data,key){
                this.data=data
                this.key=key
                // 每次实例化一个watcher的时候，先把当前的watcher实例化对象放在全局变量上保存起来，用来在订阅中心收集
                target=this
                // 调用自身的get方法，去读取当前watcher的值
                this.get()
                // 此时已经拿到值了，当前的watcher依赖也被收集了，我们要清空全局变量target
                target=null
            }
            // watcher有一个get方法，可以得到最新的值
            get(){
                return this.dat[this.key]
            }
            // 当订阅中心通知watcher更新的时候，其实是遍历所有的watcher，调用自身的重新渲染模板的方法
            update(){
                console.log(`${this.data}中的${this.key}数据发生改变了，最新的值是${this.get()},抓紧时间重新渲染模板吧`);
            }
        }

        //创建vue实例
        const vm = new Vue({
            data: {
                count: 123,
                user: {
                    name: "xiaowang",
                    age: 18
                },
                course: ["html", "js", "css"],
                person: [
                    { id: "001", name: "xiaowng" },
                    { id: "002", name: "小李" }
                ]
            }
        })
    </script>
</body>

</html>